feat: implement PATCH /players/{squadNumber} for partial updates#329
feat: implement PATCH /players/{squadNumber} for partial updates#329the-Sunny-Sharma wants to merge 5 commits into
Conversation
WalkthroughAdds RFC 7396-style PATCH support: a nullable ChangesPATCH Endpoint for Partial Player Updates
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Assessment against linked issues
Possibly related issues
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/ar/com/nanotaboada/java/samples/spring/boot/controllers/PlayersController.java`:
- Around line 250-254: In the patch method, add request validation and a
null-body guard: annotate the PlayerPatchDTO parameter with `@Valid` (e.g., public
ResponseEntity<Void> patch(`@PathVariable` Integer squadNumber, `@Valid`
`@RequestBody` PlayerPatchDTO playerPatchDTO)) and update the initial conditional
to check for null before dereferencing (e.g., if (playerPatchDTO == null ||
playerPatchDTO.getSquadNumber() != null || playerPatchDTO.getId() != null)
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();). Ensure the
controller imports javax.validation.Valid (or jakarta.validation.Valid) so DTO
constraints on PlayerPatchDTO are enforced.
In
`@src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/PlayerPatchDTO.java`:
- Around line 35-45: PlayerPatchDTO lacks Bean Validation annotations—add the
same field-level JSR-380 constraints used in PlayerDTO but using constraints
that permit null (so patch fields remain optional): annotate squadNumber with
`@Min/`@Max, dateOfBirth with `@Past`, string fields (firstName, middleName,
lastName, position, abbrPosition, team, league) with `@Size`(max=...) and any
`@Pattern`(...) used in PlayerDTO for formats, and apply any ID/UUID format
constraint used in PlayerDTO to id; do not add `@NotNull/`@NotBlank (since fields
must remain nullable). Import javax.validation.constraints.* and mirror the
exact size/regex/min-max values from PlayerDTO when updating class
PlayerPatchDTO.
In
`@src/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.java`:
- Around line 235-243: The method currently dereferences playerPatchDTO without
null-checking (see squadNumber check and the
playersRepository.findBySquadNumber(...).map(existing -> { ... }) block), so add
an early guard for playerPatchDTO == null at the start of the method (similar to
the squadNumber null check) to log a warning and return false; then proceed to
use playerPatchDTO safely in the mapping where existing.setFirstName(...),
existing.setMiddleName(...), etc. are called. Ensure the log uses the same
logger (log) and behavior as the squadNumber guard to keep semantics consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 288c1c36-cc52-413f-a575-628f4f188d18
📒 Files selected for processing (3)
src/main/java/ar/com/nanotaboada/java/samples/spring/boot/controllers/PlayersController.javasrc/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/PlayerPatchDTO.javasrc/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.java
… patch tests, CHANGELOG
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java (1)
409-410: ⚡ Quick winUse fake factories for
PlayerPatchDTOfixtures in tests.Please replace direct
new PlayerPatchDTO()setup with a test fake factory (e.g.,PlayerPatchDTOFakes) to keep fixture creation consistent and reusable.As per coding guidelines, "Ensure test data uses fake factories (PlayerFakes, PlayerDTOFakes)."
Also applies to: 431-432, 452-452
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java` around lines 409 - 410, Replace the inline fixture creation using new PlayerPatchDTO() in PlayersServiceTests with the shared test fake factory (e.g., PlayerPatchDTOFakes); instead of constructing and calling dto.setFirstName("Updated") directly, call the appropriate factory method on PlayerPatchDTOFakes to produce a DTO prepopulated or then customize it via the factory (same change for the other occurrences at lines referenced: the second dto creation and the third occurrence), ensuring tests use the standard PlayerFakes/PlayerDTOFakes classes for consistent, reusable fixtures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java`:
- Around line 405-420: The test must assert patch semantics by verifying the
saved Player actually has firstName updated and that an unrelated field remains
unchanged: use an ArgumentCaptor<Player> on playersRepositoryMock.save in
PlayersServiceTests.givenExistingPlayer_whenPatch_thenReturnsTrue to capture the
saved entity and assert saved.getFirstName().equals("Updated") and
saved.getLastName() (or saved.getSquadNumber()) equals the original entity's
value; keep the existing verify(playersRepositoryMock).findBySquadNumber(...)
and the then(actual).isTrue() assertion.
---
Nitpick comments:
In
`@src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java`:
- Around line 409-410: Replace the inline fixture creation using new
PlayerPatchDTO() in PlayersServiceTests with the shared test fake factory (e.g.,
PlayerPatchDTOFakes); instead of constructing and calling
dto.setFirstName("Updated") directly, call the appropriate factory method on
PlayerPatchDTOFakes to produce a DTO prepopulated or then customize it via the
factory (same change for the other occurrences at lines referenced: the second
dto creation and the third occurrence), ensuring tests use the standard
PlayerFakes/PlayerDTOFakes classes for consistent, reusable fixtures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9b299dd6-a598-4d37-ab83-0a6d2c7365be
📒 Files selected for processing (5)
CHANGELOG.mdsrc/main/java/ar/com/nanotaboada/java/samples/spring/boot/controllers/PlayersController.javasrc/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/PlayerPatchDTO.javasrc/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.javasrc/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java
✅ Files skipped from review due to trivial changes (1)
- CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (3)
- src/main/java/ar/com/nanotaboada/java/samples/spring/boot/controllers/PlayersController.java
- src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/PlayerPatchDTO.java
- src/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.java
|
Hi @nanotaboada, addressed all CodeRabbit review comments in the latest commit:
All 45 tests pass. Ready for review. |
|
Hi @nanotaboada, just wanted to follow up on this PR. |
|
Hi @the-Sunny-Sharma, thanks for the contribution — the feature looks well-structured and the PR description is thorough! The The project enforces ≥ 80% branch coverage as a hard gate. The new To fix this, please add tests that cover the remaining branches — for example:
You can check coverage locally by running: ./mvnw clean test jacoco:report
open target/site/jacoco/index.htmlOnce the branch coverage is back at or above 80%, the gate will pass. Happy to help if you have any questions! |
…aCoCo branch coverage gate
|
|
Hi @nanotaboada, thanks for the detailed feedback! I've pushed the additional tests to address the JaCoCo branch coverage failure. Added to
Added to
All 51 tests pass and branch coverage is now at 95% (up from 64%), well above the 80% gate. Please let me know if you'd like any further changes! |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.java (1)
596-716: ⚡ Quick winAdd tests for null body, validation failures, and empty JSON object.
The PR objectives state the controller includes a "null-body guard" and "Bean Validation on PlayerPatchDTO" with JSR-380 constraints. The current PATCH test suite does not exercise:
- Null request body — the guard mentioned in the PR should reject this with 400 Bad Request.
- Validation failures — if
PlayerPatchDTOhas constraints like@Size,@Min,@Max, or@Pastthat apply when fields are present, invalid values should trigger 422 Unprocessable Entity (similar to the PUT test at line 467).- Empty JSON object
{}— a valid JSON Merge Patch no-op that should succeed with 204 if the player exists.Adding these tests will improve branch coverage and ensure the controller's validation and guard logic is properly exercised.
🧪 Suggested test additions
Test 1: Null body
/** * Given the request body is null * When attempting to patch a player * Then response status is 400 Bad Request and service is never called */ `@Test` void givenNullBody_whenPatch_thenReturnsBadRequest() throws Exception { // Given Integer squadNumber = 10; MockHttpServletRequestBuilder request = MockMvcRequestBuilders .patch(PATH + "/{squadNumber}", squadNumber) .contentType(MediaType.APPLICATION_JSON); // When MockHttpServletResponse response = application .perform(request) .andReturn() .getResponse(); // Then verify(playersServiceMock, never()).patch(anyInt(), any(PlayerPatchDTO.class)); then(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); }Test 2: Validation failure (example with invalid
@Sizeconstraint)/** * Given the request body contains invalid field data (validation fails) * When attempting to patch a player * Then response status is 422 Unprocessable Entity and service is never called */ `@Test` void givenInvalidPatchData_whenPatch_thenReturnsUnprocessableEntity() throws Exception { // Given Integer squadNumber = 10; PlayerPatchDTO dto = new PlayerPatchDTO(); dto.setFirstName(""); // Assuming `@Size`(min=1) or similar constraint String content = objectMapper.writeValueAsString(dto); MockHttpServletRequestBuilder request = MockMvcRequestBuilders .patch(PATH + "/{squadNumber}", squadNumber) .content(content) .contentType(MediaType.APPLICATION_JSON); // When MockHttpServletResponse response = application .perform(request) .andReturn() .getResponse(); // Then verify(playersServiceMock, never()).patch(anyInt(), any(PlayerPatchDTO.class)); then(response.getStatus()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY.value()); }Test 3: Empty JSON object (no-op)
/** * Given the request body is an empty JSON object (no fields to patch) * When attempting to patch a player * Then response status is 204 No Content (no-op patch succeeds) */ `@Test` void givenEmptyPatchBody_whenPatch_thenReturnsNoContent() throws Exception { // Given Integer squadNumber = 10; PlayerPatchDTO dto = new PlayerPatchDTO(); // All fields null String content = objectMapper.writeValueAsString(dto); Mockito .when(playersServiceMock.patch(squadNumber, dto)) .thenReturn(true); MockHttpServletRequestBuilder request = MockMvcRequestBuilders .patch(PATH + "/{squadNumber}", squadNumber) .content(content) .contentType(MediaType.APPLICATION_JSON); // When MockHttpServletResponse response = application .perform(request) .andReturn() .getResponse(); // Then verify(playersServiceMock, times(1)).patch(anyInt(), any(PlayerPatchDTO.class)); then(response.getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value()); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.java` around lines 596 - 716, Add three unit tests in PlayersControllerTests covering PATCH null body, validation failure, and empty JSON no-op: (1) implement givenNullBody_whenPatch_thenReturnsBadRequest that sends a PATCH with no content to PATH+"/{squadNumber}", asserts playersServiceMock.patch is never called and response status is 400; (2) implement givenInvalidPatchData_whenPatch_thenReturnsUnprocessableEntity that posts a PlayerPatchDTO with an invalid field (e.g., empty firstName assuming `@Size`) and asserts playersServiceMock.patch is never called and response status is 422; (3) implement givenEmptyPatchBody_whenPatch_thenReturnsNoContent that posts an all-null PlayerPatchDTO, stub playersServiceMock.patch(squadNumber, dto) to return true, verify patch called once, and assert response status is 204; use objectMapper to serialize DTO, MockMvcRequestBuilders.patch to build requests, and verify/then assertions consistent with existing tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.java`:
- Around line 596-716: Add three unit tests in PlayersControllerTests covering
PATCH null body, validation failure, and empty JSON no-op: (1) implement
givenNullBody_whenPatch_thenReturnsBadRequest that sends a PATCH with no content
to PATH+"/{squadNumber}", asserts playersServiceMock.patch is never called and
response status is 400; (2) implement
givenInvalidPatchData_whenPatch_thenReturnsUnprocessableEntity that posts a
PlayerPatchDTO with an invalid field (e.g., empty firstName assuming `@Size`) and
asserts playersServiceMock.patch is never called and response status is 422; (3)
implement givenEmptyPatchBody_whenPatch_thenReturnsNoContent that posts an
all-null PlayerPatchDTO, stub playersServiceMock.patch(squadNumber, dto) to
return true, verify patch called once, and assert response status is 204; use
objectMapper to serialize DTO, MockMvcRequestBuilders.patch to build requests,
and verify/then assertions consistent with existing tests.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 66917d1e-94c3-45d7-9bd7-d51d3ebf9829
📒 Files selected for processing (2)
src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.javasrc/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java



Closes #318
What this PR does
Implements
PATCH /players/{squadNumber}for partial player updatesfollowing RFC 7396 (JSON Merge Patch) semantics.
Changes
PlayerPatchDTO— all fields nullable, annotated with@JsonInclude(NON_NULL), excludesidandsquadNumberfrom patching;JSR-380 Bean Validation constraints (
@Size,@Min,@Max,@Past)added so validation triggers only when a field is present
patch()inPlayersService— applies only non-null fieldsto the existing entity, follows existing Optional chain pattern;
null guard for both
squadNumberandplayerPatchDTO@PatchMapping("/players/{squadNumber}")inPlayersControllerwith
@Valid, full Swagger@Operation/@ApiResponsesdocumentation,and null-body guard before field access
patch()covering success (with explicit patchsemantics assertion), not found, null squad number, and null payload
CHANGELOG.mdunder[Unreleased]Acceptance criteria
PATCH /players/{squadNumber}implemented204 No Contenton success400 Bad Requestif body containssquadNumberorid404 Not Foundwhen player not foundPlayerPatchDTOCHANGELOG.mdupdated under[Unreleased]This change is
Summary by CodeRabbit
Release Notes
New Features
/players/{squadNumber}endpoint to enable partial updates of player recordsTests
Documentation